package client

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

type CtapiClient struct {
	eop        EOP
	httpClient Caller
}

func NewQueryOption() *QueryOption {
	return &QueryOption{url.Values{}}
}

type QueryOption struct {
	url.Values
}

func NewQueryParam(key, value string) QueryParam {
	return func(param *QueryOption) {
		param.Add(key, value)
	}
}

type QueryParam func(param *QueryOption)

// 生成Url
// @param queryOptions url参数
func GenerateUrl(endpoint, path string, queryParams ...QueryParam) string {

	query := NewQueryOption()
	for _, opt := range queryParams {
		opt(query)
	}

	return GenerateUrlWithOption(endpoint, path, query)

}

// 生成Url
// @param queryOption url参数
func GenerateUrlWithOption(endpoint, path string, queryOption *QueryOption) string {
	var sp string
	if len(queryOption.Values) > 0 {
		sp = "?"
	}

	return fmt.Sprint("https://", endpoint, path, sp, queryOption.Encode())
}

type Caller interface {
	Post(ctx context.Context, url string, header http.Header, body []byte) *Response
	Delete(ctx context.Context, url string, header http.Header, body []byte) *Response
	Patch(ctx context.Context, url string, header http.Header, body []byte) *Response
	Put(ctx context.Context, url string, header http.Header, body []byte) *Response
	Get(ctx context.Context, url string, header http.Header) *Response
	GetWithEmptyBody(ctx context.Context, url string, header http.Header) *Response
}

func NewCtapiClient(ak string, sk string, opts ...HttpOption) *CtapiClient {
	return &CtapiClient{
		eop:        NewEOP(ak, sk),
		httpClient: NewHttpClient(opts...),
	}
}

func (c *CtapiClient) Delete(ctx context.Context, address string, headers http.Header, body interface{}) (*Response, error) {
	signHeaders, err := c.signHeaders(address, headers, body)
	if err != nil {
		return nil, err
	}

	bodyBs := make([]byte, 0)
	if body != nil {
		if bytes, ok := body.([]byte); ok {
			bodyBs = bytes
		} else {
			bodyBs, err = json.Marshal(body)
			if err != nil {
				return nil, err
			}
		}
	}
	return c.httpClient.Delete(ctx, address, signHeaders, bodyBs), nil
}

func (c *CtapiClient) Get(ctx context.Context, address string, headers http.Header) (*Response, error) {
	signHeaders, err := c.signHeaders(address, headers, nil)
	if err != nil {
		return nil, err
	}
	return c.httpClient.Get(ctx, address, signHeaders), nil
}

func (c *CtapiClient) Patch(ctx context.Context, address string, headers http.Header, body interface{}) (*Response, error) {
	signHeaders, err := c.signHeaders(address, headers, body)
	if err != nil {
		return nil, err
	}
	bodyBs := make([]byte, 0)
	if body != nil {
		if bytes, ok := body.([]byte); ok {
			bodyBs = bytes
		} else {
			bodyBs, err = json.Marshal(body)
			if err != nil {
				return nil, err
			}
		}
	}
	return c.httpClient.Patch(ctx, address, signHeaders, bodyBs), nil
}

func (c *CtapiClient) Post(ctx context.Context, address string, headers http.Header, body interface{}) (*Response, error) {
	signHeaders, err := c.signHeaders(address, headers, body)
	if err != nil {
		return nil, err
	}
	bodyBs := make([]byte, 0)
	if body != nil {
		if bytes, ok := body.([]byte); ok {
			bodyBs = bytes
		} else {
			bodyBs, err = json.Marshal(body)
			if err != nil {
				return nil, err
			}
		}
	}
	return c.httpClient.Post(ctx, address, signHeaders, bodyBs), nil
}

func (c *CtapiClient) Put(ctx context.Context, address string, headers http.Header, body interface{}) (*Response, error) {
	signHeaders, err := c.signHeaders(address, headers, body)
	if err != nil {
		return nil, err
	}
	bodyBs := make([]byte, 0)
	if body != nil {
		if bytes, ok := body.([]byte); ok {
			bodyBs = bytes
		} else {
			bodyBs, err = json.Marshal(body)
			if err != nil {
				return nil, err
			}
		}
	}
	return c.httpClient.Put(ctx, address, signHeaders, bodyBs), nil
}

func (c *CtapiClient) signHeaders(address string, inputHeaders http.Header, body interface{}) (http.Header, error) {
	u, err := url.Parse(address)
	if err != nil {
		return nil, err
	}
	queryParams := u.Query()
	queryMap := make(map[string]interface{})
	for key, values := range queryParams {
		if len(values) > 0 {
			queryMap[key] = values[0]
		}
	}
	signHeaders := c.eop.GetSignHeaders(queryMap, body)

	mergeHeaders := http.Header{}
	if inputHeaders != nil {
		mergeHeaders = inputHeaders.Clone()
	}
	for k, v := range signHeaders {
		mergeHeaders.Set(k, v[0])
	}
	return mergeHeaders, nil
}
